﻿module BoardTool.DirTree

open System
open System.Collections.Generic

let BOARDS_PATTERN = "*.board"

type IDirJobObserver =
    abstract member onBeginDir : string -> unit
    abstract member onEndDir : string -> unit
    abstract member onCreateDir : string -> unit
    abstract member onFilesProcessed : int -> unit

let mapFiles filePattern (v : string -> 'T) (obs : IDirJobObserver) (root : string) =
    let results = Dictionary<string, 'T>()

    let rec mapDirRec dir =
        obs.onBeginDir( dir )

        IO.Directory.EnumerateFiles( dir, filePattern )
        |> Seq.map (fun file -> async { return (file, v file) })
        |> Async.Parallel
        |> Async.RunSynchronously
        |> Array.iter (fun (file, res) -> results.[file] <- res)

        obs.onFilesProcessed( results.Count ) 

        IO.Directory.EnumerateDirectories( dir )
        |> Seq.iter mapDirRec

        obs.onEndDir( dir )
        
    mapDirRec root
    results

let mapBoards (v : string -> 'T) (obs : IDirJobObserver) (root : string) =
    mapFiles BOARDS_PATTERN v obs root

let isDirSeparator ch =
    ch = IO.Path.DirectorySeparatorChar || ch = IO.Path.AltDirectorySeparatorChar

let stripUpperDir (upper : string) (path : string) =
    if not( path.StartsWith( upper )) then
        path
    else
        let path' = path.Substring( upper.Length )
        let path' = if isDirSeparator path'.[0] then path'.Substring( 1 ) else path'
        path'

let getTransformedPath baseRoot trRoot basePath =
    IO.Path.Combine( trRoot, stripUpperDir baseRoot basePath )

let transformFiles filePattern (tr : string -> string -> 'T) (obs : IDirJobObserver) srcRoot destRoot =
    let transformed = Dictionary<string, 'T>()

    let createDir dir = 
        if not( IO.Directory.Exists( dir )) then
            obs.onCreateDir dir
            IO.Directory.CreateDirectory( dir ) |> ignore

    let transFileAsynch srcFile = 
        async { 
            let destFile = getTransformedPath srcRoot destRoot srcFile
            return (srcFile, tr srcFile destFile) 
        }

    let rec transDirRec srcDir destDir =
        obs.onBeginDir( srcDir + " -> " + destDir )

        IO.Directory.EnumerateFiles( srcDir, filePattern )
        |> Seq.map transFileAsynch
        |> Async.Parallel
        |> Async.RunSynchronously
        |> Array.iter (fun (srcFile, trRes) -> transformed.[srcFile] <- trRes)

        obs.onFilesProcessed( transformed.Count )

        IO.Directory.EnumerateDirectories( srcDir )
        |> Seq.iter transSubdir

        obs.onEndDir( srcDir + " -> " + destDir )

    and transSubdir srcDir =
        let destDir = getTransformedPath srcRoot destRoot srcDir

        createDir destDir
        transDirRec srcDir destDir
        
    createDir destRoot
    transDirRec srcRoot destRoot
    transformed

let transformBoards tr obs srcRoot destRoot = 
    transformFiles BOARDS_PATTERN tr obs srcRoot destRoot

// TODO refactor?
let consoleLogger() =
    { new IDirJobObserver with
        member this.onBeginDir( dir ) =
            eprintfn "Working directory: %s" dir
        member this.onEndDir( _ ) = ()
        member this.onFilesProcessed( nFiles ) =
            eprintfn "... Processed %d files so far ... " nFiles
        member this.onCreateDir( dir ) =
            eprintfn "Created directory: %s" dir
    }

let getUniqueFileName file =
    let mutable found = IO.File.Exists( file )
    let mutable i = 1

    let dir = IO.Path.GetDirectoryName( file )
    let name = IO.Path.GetFileNameWithoutExtension( file )
    let ext = IO.Path.GetExtension( file )
    let mutable uniqueName = file
            
    while not found do
        let fullName = IO.Path.Combine( dir, name + string(i) + ext )
        if not <| IO.File.Exists( fullName ) then
            found <- true
            uniqueName <- fullName
        else
            i <- i + 1
    uniqueName

let getPostfixedFile (postfix : string) (file : string) =
    let dir = IO.Path.GetDirectoryName( file )
    let nameNoExt = IO.Path.GetFileNameWithoutExtension( file )
    let ext = IO.Path.GetExtension( file )
    let name' = nameNoExt + postfix + ext
    let file' = IO.Path.Combine( dir, name' )
    let file' = getUniqueFileName file'
    file'
